﻿using System;
using System.Collections ; 
using System.Collections.Generic;
using System.IO; 
using System.Text;

namespace Parser
{

    public class TokenParser3 
    {
        /* this is a regular expression parser generator.
         * 
         * literal = single quoted string - within which regular escape works. 
         * 
         * singleQuoteString = '\'' singleQuoteChar* '\'' => singleQuoteChar* (this is what's returned)
         * 
         * singleQuoteChar = '\\\'' => '\''
         *  | '\\r' => '\r' 
         *  | '\\n' => '\n'
         *  | '\\b' => '\b'
         *  | '\\t' => '\t' 
         *  | '\\f' => '\f' 
         *  | '\\' . => . // . matches any char. 
         *  | ~'\''  // ~ matches the NOT case. 
         * 
         */

        private static readonly IParser matchWhiteSpace =
            Combinator.TransformSequence(delegate(object[] o)
        {
            return new Token("WHITESPACE", o);
        }
        , Combinator.OneMany(CharParser.OneOf(' ', '\r', '\n', '\t', '\f')));
        
        private static readonly IParser singleQuote = CharParser.Equals('\'');

        private static readonly IParser notSingleQuote = CharParser.NotEquals('\'');

        private static readonly IParser escape = CharParser.Equals('\\');

        private static readonly IParser notEscape = CharParser.NotEquals('\\');

        private static readonly IParser escapedChar =
            Combinator.TransformSequence(delegate(object[] chars) {
                return chars[1];
            }
            , escape
            , CharParser.Any);

        private static readonly IParser literalChar =
            Combinator.Transform(Combinator.Choice(Combinator.MapCharsTo("\\\r", '\r')
                , Combinator.MapCharsTo("\\\b", '\b')
                , Combinator.MapCharsTo("\\\n", '\n')
                , Combinator.MapCharsTo("\\\f", '\f')
                , Combinator.MapCharsTo("\\\t", '\t')
                , Combinator.MapCharsTo("\\s", ' ')
                , escapedChar
                , notSingleQuote), delegate(object o)
        {
            return new Token("CHAR", o); 
        });

        private static readonly IParser notChar =
            Combinator.TransformArray(Combinator.Sequence(CharParser.Equals('~'), literalChar)
                , delegate(object[] chars)
        {
            return new Token("NOT_CHAR", ((Token)chars[1]).Value); 
        });

        /* meta characters */
        private static readonly IParser matchAny = Combinator.Transform(CharParser.Equals('.'), delegate(object o)
        {
            return new Token("ANY", o); 
        });

        private static readonly IParser matchZeroOne = Combinator.Transform(CharParser.Equals('?'), delegate(object o)
        {
            return new Token("ZERO_ONE", o); 
        });

        private static readonly IParser matchZeroMany = Combinator.Transform(CharParser.Equals('*'), delegate(object o)
        {
            return new Token("ZERO_MANY", o); 
        });

        private static readonly IParser matchOneMany = Combinator.Transform(CharParser.Equals('+'), delegate(object o)
        {
            return new Token("ONE_MANY", o); 
        }); 

        private static readonly IParser choiceChar = Combinator.Transform(CharParser.Equals('|'), delegate(object o){
            return new Token("CHOICE", o); 
        }); 
        
        // I can build this without using the following... it would be *scary*... 
        private static readonly IParser makeLiteralCharParser
            = Combinator.Transform(Combinator.Choice(Combinator.MapCharsTo("\\\r", '\r')
                , Combinator.MapCharsTo("\\\b", '\b')
                , Combinator.MapCharsTo("\\\n", '\n')
                , Combinator.MapCharsTo("\\\f", '\f')
                , Combinator.MapCharsTo("\\\t", '\t')
                , Combinator.MapCharsTo("\\s", ' ')
                , escapedChar 
                , notSingleQuote)
            , delegate(object o)
        {
            return CharParser.Equals((char)o); 
        }); 

        private static readonly IParser makeNotCharParser
            = Combinator.TransformSequence(delegate(object[] chars) {
                return CharParser.NotEquals((char)chars[1]); 
            }
            , CharParser.Equals('~'), literalChar);

        private static readonly IParser makeAnyCharParser
            = Combinator.Transform(CharParser.Equals('.')
            , delegate(object o)
        {
            return CharParser.Any;
        });

        private static readonly IParser makeCharParser 
            = Combinator.Choice(makeNotCharParser, makeLiteralCharParser, makeAnyCharParser);

        private static readonly IParser makeZeroOneParser
            = Combinator.TransformSequence(delegate(object[] zeroOne)
        {
            return Combinator.ZeroOne((IParser)zeroOne[0], ""); 
        }
            , makeCharParser, CharParser.Equals('?'));
 
        private static readonly IParser makeZeroManyParser
            = Combinator.TransformSequence(delegate(object[] zeroMany)
            {
                return Combinator.ZeroMany((IParser)zeroMany[0]);
            }
            , makeCharParser, CharParser.Equals('*'));

        private static readonly IParser makeOneManyParser
            = Combinator.TransformSequence(delegate(object[] oneMany)
            {
                return Combinator.OneMany((IParser)oneMany[0]);
            }
            , makeCharParser, CharParser.Equals('+'));

        private static readonly IParser makeExpressionParser
            = Combinator.Choice(makeZeroOneParser, makeZeroManyParser, makeOneManyParser, makeCharParser); 

        private static readonly IParser makeAlternativeParser
            = Combinator.Transform(Combinator.Delimited(makeExpressionParser, CharParser.Equals('|'))
            , delegate(object exps)
        {
            // it's a list of expressionsparse
            return Combinator.MakeChoice(Array.ConvertAll<object, IParser>((object[])exps, delegate(object o) { return (IParser)o; }));
        });

        private static readonly IParser makeNestedAlternativeParser
            = Combinator.TransformSequence(delegate(object[] exps)
        {
            return exps[1]; 
        }
            , CharParser.Equals('(')
            , makeAlternativeParser
            , CharParser.Equals(')'));

        static TokenParser3()
        {
            ((ChoiceParser)makeExpressionParser).Prepend(makeAlternativeParser);
            ((ChoiceParser)makeExpressionParser).Prepend(makeNestedAlternativeParser);
        }

        public static IParser Compile2(string tokenType, string expression) {
            using (BinaryReader reader = new BinaryReader(StringUtil.StringToStream(expression)))
            {
                IParser grammar = Combinator.TransformArray(Combinator.OneMany(makeExpressionParser), delegate(object[] o)
                {
                    return new Token(tokenType, TransformParser.AtomToString(o)); 
                }); 
                Result result = grammar.Parse(reader, 0);
                if (Result.Succeeded(result))
                {
                    return (IParser)result.Inner;
                }
                else
                {
                    throw new InvalidDataException(string.Format("Invalid regular expression: {0}", expression));
                }
            }
        }

        private static readonly IParser tokenParser = Combinator.SkipOne(matchWhiteSpace, Combinator.Choice(matchAny, matchOneMany, matchWhiteSpace, matchZeroMany, matchZeroOne, notChar, literalChar));




        private static IParser compileTokens(Token[] tokens, string tokenType, string s)
        {
            // walk through the tokens. 
            ArrayList list = new ArrayList(); 
            IParser prev; 
            foreach (Token token in tokens) {
                switch (token.Type)
                {
                    case "CHAR":
                        list.Add(CharParser.Equals((char)token.Value));
                        break;
                    case "NOT_CHAR":
                        list.Add(CharParser.NotEquals((char)token.Value));
                        break;
                    case "ANY":
                        list.Add(CharParser.Any);
                        break;
                    case "WHITESPACE": // skip over whitespaces... 
                        break;
                    case "ONE_MANY":
                        prev = previousParser(list, s);
                        list.Add(Combinator.OneMany(prev));
                        break;
                    case "ZERO_MANY":
                        prev = previousParser(list, s);
                        list.Add(Combinator.ZeroMany(prev));
                        break;
                    case "ZERO_ONE":
                        prev = previousParser(list, s);
                        list.Add(Combinator.ZeroOne(prev, ""));
                        break;
                    default:
                        throw new InvalidDataException(string.Format("Unknown Token Type: {0}", token.Type));
                }
            }
            if (list.Count == 0)
                throw new InvalidDataException(string.Format("invalid regular expression: empty expression '{0}'", s));
            // otherwise we can use this to form a sequence! 
            return Combinator.TransformArray(Combinator.MakeSequence((IParser[])list.ToArray(typeof(IParser)))
                , delegate(object[] parsed)
                {
                    return new Token(tokenType, TransformParser.AtomToString(parsed));
                });
        }

        public static IParser Compile(string tokenType, string s)
        {
            using (BinaryReader reader = new BinaryReader(StringUtil.StringToStream(s))) {
                IParser compilerParser = 
                    Combinator.TransformArray(Combinator.OneMany(tokenParser), delegate(object[] tokens)
                {
                    return compileTokens(Array.ConvertAll<object, Token>(tokens, delegate(object o) { return (Token)o; }), tokenType, s); 
                });
                Result result = compilerParser.Parse(reader, 0);
                if (Result.Succeeded(result))
                {
                    return (IParser)result.Inner; 
                }
                else
                {
                    throw new InvalidDataException(string.Format("Invalid regular expression: {0}", s)); 
                }
            }
        }

        private static IParser previousParser(IList list, string exp)
        {
            // we need to have one from the previous... 
            if (list.Count == 0)
            {
                throw new InvalidDataException(string.Format("* must be preceded by expression: {0}", exp));
            }
            // pop the previous one and replace it with one many.
            IParser prev = (IParser)list[list.Count - 1];
            list.Remove(list[list.Count - 1]);
            return prev; 
        }

        private static char[] ObjectArrayToCharArray(object[] ary)
        {
            return Array.ConvertAll<object, char>(ary, delegate(object o) { return (char)o; });
        }
    }


}
